<?php

/*
 * Torrent class
 */

require "torrent_parse/torrent_parse.php";
require "torrent_scrape/httptscraper.php";

class torrent {

    private $config;
    private $db;
    private $sort_main;
    private $log;
    private $torrent_parse;
    private $torrent_scrape;

    public function __construct($config, $db, $sort_main) {
        $this->config = $config;
// not really a good detection, as it might be OSX or something else
        if (defined('PHP_WINDOWS_VERSION_MAJOR'))
            $this->os = "windows";
        else
            $this->os = "linux";
        $this->db = $db;
        $this->sort_main = $sort_main;
        if (!class_exists("imdb_api"))
            die("IMDB not instantiated");
        $this->log = new log($config, $db);
        $this->torrent_scrape = new httptscraper;
    }

    function curl_get_file_size($url) {
        // Assume failure.
        $result = -1;

        $curl = curl_init($url);

        // Issue a HEAD request and follow any redirects.
        curl_setopt($curl, CURLOPT_NOBODY, true);
        curl_setopt($curl, CURLOPT_HEADER, true);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 GTB5');

        $data = curl_exec($curl);
        curl_close($curl);

        if ($data) {
            $content_length = "unknown";
            $status = "unknown";

            if (preg_match("/^HTTP\/1\.[01] (\d\d\d)/", $data, $matches)) {
                $status = (int) $matches[1];
            }

            if (preg_match("/Content-Length: (\d+)/", $data, $matches)) {
                $content_length = (int) $matches[1];
            }

            if ($status == 200 || ($status > 300 && $status <= 308)) {
                $result = $content_length;
            }
        }

        return $result;
    }

    public function get_file($file, $maxlength = false) {
        $chunk_size = 4096;
        $fh = fopen($file, "r");
        if (!$fh)
            return false;
        $content = "";
        $readed = 0;
        while (!feof($fh)) {
            $content.=fread($fh, $chunk_size);
            $readed+=strlen($content);
            if ($maxlength && $readed > $maxlength) {
                fclose($fh);
                return false;
            }
        }
        fclose($fh);
        return $content;
    }

    /*
     * Convert XML to Array
     */

    public function xmlstr_to_array($xmlstr) {
        $doc = new DOMDocument();
        $doc->loadXML($xmlstr);
        return $this->domnode_to_array($doc->documentElement);
    }

    /*
     * Convert domnode to array
     */

    public function domnode_to_array($node) {
        $output = array();
        switch ($node->nodeType) {
            case XML_CDATA_SECTION_NODE:
            case XML_TEXT_NODE:
                $output = trim($node->textContent);
                break;
            case XML_ELEMENT_NODE:
                for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) {
                    $child = $node->childNodes->item($i);
                    $v = $this->domnode_to_array($child);
                    if (isset($child->tagName)) {
                        $t = $child->tagName;
                        if (!isset($output[$t])) {
                            $output[$t] = array();
                        }
                        $output[$t][] = $v;
                    } elseif ($v) {
                        $output = (string) $v;
                    }
                }
                if (is_array($output)) {
                    if ($node->attributes->length) {
                        $a = array();
                        foreach ($node->attributes as $attrName => $attrNode) {
                            $a[$attrName] = (string) $attrNode->value;
                        }
                        $output['@attributes'] = $a;
                    }
                    foreach ($output as $t => $v) {
                        if (is_array($v) && count($v) == 1 && $t != '@attributes') {
                            $output[$t] = $v[0];
                        }
                    }
                }
                break;
        }
        return $output;
    }

    /*
     * find match for tv to follow to not
     */

    private function tv_match($name) {
        foreach ($this->config['Series']['follow'] as $follow) {
            if (strstr(strtolower($name), strtolower($follow)))
                return true;
        }
        return false;
    }

    /*
     * Process kickass torrent rss
     */

    public function process_kickass_rss() {
        $feedurl = $this->config["Torrent"]["kickass_feed"];
        $ctx = stream_context_create(
                array(
                    'http' => array(
                        'timeout' => 3
                    )
                )
        );
        $maxlen = 4096768;  //4MB maximum size of torrent
        $feed_content = file_get_contents($feedurl, false, $ctx);
        $feed_content = gzdecode($feed_content);
        if (!$feed_content) {
            $this->log->addmessage("Feed {$feedurl} unavailable!");
            return false;
        }
        $afeed_content = $this->xmlstr_to_array($feed_content);
        $items = $afeed_content["channel"]["item"];
        foreach ($items as $item) {
            $url = $item["enclosure"]["@attributes"]["url"];
            if (substr($url, 0, 29) == "https://torcache.net/torrent/" || substr($url, 0, 28) == "http://torcache.net/torrent/") {
                $title = $item["title"];
                $tv = $this->sort_main->tv_parse($title);
                $title_clean = $this->sort_main->parse_name($title);
                if (!$title_clean && !$tv)  // non-valid content
                    continue;
                if ($tv)
                    $pass = $this->sort_main->check_filters_tv($title);
                else
                    $pass = $this->sort_main->check_filters($title);
                if (!$pass && !$tv) {
                    $this->log->addmessage("Torrent {$title} does not meet minimum requirements!");
                    continue;
                }
                if ($tv) {
                    if (!$this->tv_match($tv["name"])) {   // tv to follow
                        $this->log->addmessage("Torrent tv {$tv["name"]} not in follow list");
                        continue;
                    }
                    $existing = $this->db->downloaded_content_exist($tv["name"], $tv["series"], $tv["episode"], true, true);
                }
                else
                    $existing = $this->db->downloaded_content_exist($title_clean, false, false, true, true);
                if ($existing) {   // already existing
                    $this->log->addmessage("Torrent {$title_clean} already downloading or downloaded");
                    continue;
                }
                $url_nosecure = str_replace("https://", "http://", $url);
                $this->log->addmessage("Get torrent {$url_nosecure}");
                //$fs = $this->curl_get_file_size($url_nosecure);
                //if ($fs > $maxlen) {
                //    $this->log->addmessage("Torrent {$url} too long ({$fs}), rejecting!");
                //    continue;
                //}
                //$torrent_content = file_get_contents($url_nosecure, false, $ctx, -1, $maxlen);
                $torrent_content = $this->get_file($url_nosecure, $maxlen);
                //$fs = strlen($torrent_content);
                if (!$torrent_content) {    // no torrent content
                    $this->log->addmessage("Torrent {$url} unavailable, or wrong size!");
                    continue;
                }
                $torrent_content = gzdecode($torrent_content);
                $this->save_torrent($title, $torrent_content, $title_clean, $tv, $url_nosecure);
            }
        }
        return true;
    }

    /*
     * Process rss for piratebay
     */

    public function process_piratebay_rss() {
        $feedurl = $this->config["Torrent"]["piratebay_feed"];
        $ctx = stream_context_create(array(
            'http' => array(
                'timeout' => 3
            )
                )
        );
        if (DEBUG_SCRIPT)
            $this->log->addmessage("Fetching new rss feeds from {$feedurl}");
        $feed_content = file_get_contents($feedurl, false, $ctx);
        if (!$feed_content) {
            $this->log->addmessage("Feed {$feedurl} unavailable!");
            return false;
        }
        //$afeed_content = json_decode(json_encode((array) simplexml_load_string($feed_content)), 1);
        $afeed_content = $this->xmlstr_to_array($feed_content);
        $items = $afeed_content["channel"]["item"];
        foreach ($items as $item) {
            if (substr($item["link"], 0, 7) == "magnet:") {
                $title = $item["title"];
                $tv = $this->sort_main->tv_parse($title);
                $title_clean = $this->sort_main->parse_name($title);
                if (!$title_clean)  // non-valid content
                    continue;
                if ($tv)
                    $pass = $this->sort_main->check_filters_tv($title);
                else
                    $pass = $this->sort_main->check_filters($title);
                if (!$pass && !$tv) {
                    $this->log->addmessage("Torrent {$title} does not meet minimum requirements!");
                    continue;
                }
                if ($tv) {
                    if (!$this->tv_match($tv["name"])) {   // tv to follow
                        $this->log->addmessage("Torrent tv {$tv["name"]} not in follow list");
                        continue;
                    }
                    $existing = $this->db->downloaded_content_exist($tv["name"], $tv["series"], $tv["episode"], true, true);
                }
                else
                    $existing = $this->db->downloaded_content_exist($title_clean, false, false, true, true);
                if ($existing) {   // already existing
                    $this->log->addmessage("Torrent {$title_clean} already downloading or downloaded");
                    continue;
                }
                $itemparts = explode("&dn=", $item["link"]);
                $tname = $itemparts[1];
                $tname = urldecode($tname);
                $this->save_torrent($tname, $item["link"], $title_clean, $tv, $tname);
            }
        }
        return true;
    }

    /**
     * Check if torrent content comply and save it
     * 
     * 
     * @param type $filename
     * @param type $content
     * @param type $name
     * @param type $tv
     * @param type $torrent_url
     * @return boolean
     */
    private function save_torrent($filename, $content, $name, $tv, $torrent_url) {
        $this->torrent_parse = new torrent_parse($content);
        $tname = $this->torrent_parse->name();
        $tcomment = $this->torrent_parse->comment();
        $tsize = $this->torrent_parse->size();
        $tcontent = $this->torrent_parse->content();
        $thashinfo = $this->torrent_parse->hash_info();
        $tannounce = $this->torrent_parse->announce();
        $tfiles = $this->torrent_parse->content();
        $archs = 0;
        foreach ($tfiles as $tfilename => $tfilesize) {
            if (strstr($tfile, ".zip") || strstr($tfile, ".rar") || strstr($tfile, ".7z"))
                $archs++;
        }
        if ($archs > 2) {
            $this->log->addmessage("Too many archives inside torrent, probalby fake!");
            return false;
        }
        if ($tv) {
            $minsize = $this->sort_main->size_to_number($this->config["Series"]["minimum_file_size"]);
            $maxsize = $this->sort_main->size_to_number($this->config["Series"]["maximum_file_size"]);
        } else {
            $minsize = $this->sort_main->size_to_number($this->config["Quality"]["minimum_file_size"]);
            $maxsize = $this->sort_main->size_to_number($this->config["Quality"]["maximum_file_size"]);
        }
        if ($tsize < $minsize || $tsize > $maxsize) {
            $this->log->addmessage("Torrent size {$tsize} not in range of [$minsize .. $maxsize]");
            return false;
        }
        $ret = false;
        $seeds = $peers = 0;
        $timeout = 2;
        $maxread = 1024 * 4;    //Read only 4MiB of the scrape response
        foreach ($tannounce as $ta) {
            $scraper = new httptscraper($timeout, $maxread);
            $ret = $scraper->scrape($ta[0], array($thashinfo));
            if ($ret) {
                $seeds = $ret[$thashinfo]["seeders"];
                $peers = $ret[$thashinfo]["leechers"];
                break;
            }
        }
        $save = $this->config["Directories"]["automatic_torrents"] . "/" . $filename . ".torrent";
        if (!$tv && (!$this->sort_main->imdb_info || $this->sort_main->imdb_info->code == 404)) {
            $this->log->addmessage("Torrent name not found in IMDB, skipping");
            return false;
        }
        if (DEBUG_SCRIPT)
            $this->log->addmessage("Save torrent file to {$save}");
        if (!DEBUG_SIMULATE)
            $fput = file_put_contents($save, $content);
        if (!DEBUG_SIMULATE)
            if (!$fput) {
                $this->log->addmessage("Cannot write torrent to {$save}");
                return false;
            }
        $quality = $this->sort_main->quality;
        if ($tv)
            $this->db->torrent_add_new_content($tv["name"], $tv["season"], $tv["episode"], $tsize, $this->sort_main->imdb_info, $quality, $torrent_url, $seeds, $peers);
        else
            $this->db->torrent_add_new_content($name, "", "", $tsize, $this->sort_main->imdb_info, $quality, $torrent_url, $seeds, $peers);
    }

    /*
     * Run main code
     */

    public function run() {
        if ((int) $this->config["Torrent"]["use_torrents"] == false)
            return false;
        if ((int) $this->config["Torrent"]["use_kickass_torrent"]) {
            $this->process_kickass_rss();
        } else
        if ((int) $this->config["Torrent"]["use_piratebay_torrent"]) {
            $this->process_piratebay_rss();
        }
    }

}

?>
